/**
 * Copyright 2008 Adam Ruggles.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package kiff.enumerator;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

import org.hibernate.HibernateException;
import org.hibernate.type.NullableType;
import org.hibernate.type.TypeFactory;
import org.hibernate.usertype.ParameterizedType;
import org.hibernate.usertype.UserType;

/**
 * GenericEnumUserType - from http://www.hibernate.org/272.html.
 * @author Adam
 * @version $Id: GenericEnumUserType.java 38 2008-09-21 07:40:51Z a.ruggles $	
 */
public class GenericEnumUserType implements UserType, ParameterizedType {
    /**
     * The default identifier method name.
     */
    private static final String DEFAULT_IDENTIFIER_METHOD_NAME = "name";
    /**
     * The default value of method name.
     */
    private static final String DEFAULT_VALUE_OF_METHOD_NAME = "valueOf";
    /**
     * The Enumerator Class.
     */
    @SuppressWarnings("unchecked")
    private Class<? extends Enum> enumClass;
    /**
     * The identifier Method.
     */
    private Method identifierMethod;
    /**
     * The Identifier Type.
     */
    private Class<?> identifierType;
    /**
     * Array of SQL Types.
     */
    private int[] sqlTypes;
    /**
     * The null type.
     */
    private NullableType type;
    /**
     * The value of method.
     */
    private Method valueOfMethod;

    /**
     * @see org.hibernate.usertype.UserType#assemble(java.io.Serializable, java.lang.Object)
     */
    public Object assemble(final Serializable cached, final Object owner) throws HibernateException {
        return cached;
    }
    /**
     * @see org.hibernate.usertype.UserType#deepCopy(java.lang.Object)
     */
    public Object deepCopy(final Object value) throws HibernateException {
        return value;
    }
    /**
     * @see org.hibernate.usertype.UserType#disassemble(java.lang.Object)
     */
    public Serializable disassemble(final Object value) throws HibernateException {
        return (Serializable) value;
    }
    /**
     * @see org.hibernate.usertype.UserType#equals(java.lang.Object, java.lang.Object)
     */
    public boolean equals(final Object x, final Object y) throws HibernateException {
        return x == y;
    }
    /**
     * @see org.hibernate.usertype.UserType#hashCode(java.lang.Object)
     */
    public int hashCode(final Object x) throws HibernateException {
        return x.hashCode();
    }
    /**
     * @see org.hibernate.usertype.UserType#isMutable()
     */
    public boolean isMutable() {
        return false;
    }
    /**
     * @see org.hibernate.usertype.UserType#nullSafeGet(java.sql.ResultSet, java.lang.String[], java.lang.Object)
     */
    public Object nullSafeGet(final ResultSet rs, final String[] names, final Object owner)
    	throws HibernateException, SQLException {  
        Object identifier = type.get(rs, names[0]);
        if (identifier == null) {
            return null;
        }
        
        try {
            return valueOfMethod.invoke(enumClass, new Object[] { identifier });
            // return valueOfMethod.invoke(enumClass.getEnumConstants()[0], new Object [] {identifier});
        } catch (Exception e) {
            throw new HibernateException("Exception while invoking valueOf method '" + valueOfMethod.getName()
            		+ "' of " + "enumeration class '" + enumClass + "'", e);
        }
    }
    /**
     * @see org.hibernate.usertype.UserType#nullSafeSet(java.sql.PreparedStatement, java.lang.Object, int)
     */
    public void nullSafeSet(final PreparedStatement st, final Object value, final int index)
    	throws HibernateException, SQLException {
        try {
            if (value == null) {
                st.setNull(index, type.sqlType());
            } else {
                Object identifier = identifierMethod.invoke(value, new Object[0]);
                type.set(st, identifier, index);
            }
        } catch (Exception e) {
            throw new HibernateException("Exception while invoking identifierMethod '" + identifierMethod.getName()
            		+ "' of " + "enumeration class '" + enumClass + "'", e);
        }
    }
    /**
     * @see org.hibernate.usertype.UserType#replace(java.lang.Object, java.lang.Object, java.lang.Object)
     */
    public Object replace(final Object original, final Object target, final Object owner) throws HibernateException {
        return original;
    }
    /**
     * @see org.hibernate.usertype.UserType#returnedClass()
     */
    @SuppressWarnings("unchecked")
    public Class returnedClass() {
        return enumClass;
    }
    /**
     * @see org.hibernate.usertype.ParameterizedType#setParameterValues(java.util.Properties)
     */
    public void setParameterValues(final Properties parameters) {
        String enumClassName = parameters.getProperty("enumClass");
        try {
            enumClass = Class.forName(enumClassName).asSubclass(Enum.class);
        } catch (ClassNotFoundException cfne) {
            throw new HibernateException("Enum class not found", cfne);
        }

        String identifierMethodName = parameters.getProperty("identifierMethod", DEFAULT_IDENTIFIER_METHOD_NAME);

        try {
            identifierMethod = enumClass.getMethod(identifierMethodName, new Class[0]);
            identifierType = identifierMethod.getReturnType();
        } catch (Exception e) {
            throw new HibernateException("Failed to obtain identifier method", e);
        }

        type = (NullableType) TypeFactory.basic(identifierType.getName());

        if (type == null) {
            throw new HibernateException("Unsupported identifier type " + identifierType.getName());
        }

        sqlTypes = new int[] { type.sqlType() };

        String valueOfMethodName = parameters.getProperty("valueOfMethod", DEFAULT_VALUE_OF_METHOD_NAME);

        try {
            valueOfMethod = enumClass.getMethod(valueOfMethodName, new Class[] { identifierType });
        } catch (Exception e) {
            throw new HibernateException("Failed to obtain valueOf method", e);
        }
    }
    /**
     * @see org.hibernate.usertype.UserType#sqlTypes()
     */
    public int[] sqlTypes() {
        return sqlTypes;
    }
}
